<template>
	<div>
		实时转写：
		<div>
			<!-- 按钮 -->

			<el-button type="primary" @click="btnclick">{{
				btnStatusShow
			}}</el-button>
			<!-- | <el-button @click="recPlay">本地试听</el-button>  -->

			<el-divider direction="vertical"></el-divider>
			<el-button type="primary" @click="recdown">下载文件</el-button>
		</div>
		<!-- <div style="padding-top: 5px">
			
			<div style="border: 1px solid #ccc; display: inline-block">
				<div style="height: 100px; width: 300px" ref="recwave"></div>
			</div>
		</div> -->

		<div class="result-area">
			{{ resultText }}
		</div>
		非实时转写：
		<div class="divider">
			<el-select v-model="typeAsr" style="width: 80px">
				<el-option value="1" label="百度">百度</el-option>
				<el-option value="2" label="阿里">阿里</el-option>
				<el-option value="3" label="讯飞">讯飞</el-option> </el-select
			>&nbsp;
			<el-button type="primary" @click="recognition">非实时转写</el-button>
		</div>

		<div class="result-area">
			{{ resultTextOffline }}
		</div>
	</div>
</template>
<script setup>
	import { ref, onMounted, onBeforeUnmount } from "vue";
	//必须引入的核心
	import Recorder from "recorder-core";
	import { ElMessage } from "element-plus";
	//引入mp3格式支持文件；如果需要多个格式支持，把这些格式的编码引擎js文件放到后面统统引入进来即可
	import "recorder-core/src/engine/mp3";
	import "recorder-core/src/engine/wav";
	import "recorder-core/src/engine/mp3-engine";
	import "recorder-core/src/engine/pcm";
	//录制wav格式的用这一句就行
	//import 'recorder-core/src/engine/wav'

	//可选的插件支持项，这个是波形可视化插件
	import "recorder-core/src/extensions/waveview";

	//ts import 提示：npm包内已自带了.d.ts声明文件（不过是any类型）
	const rec = ref(undefined);
	const recwave = ref(null);
	const wave = ref(undefined);
	const recBlob = ref(undefined);
	const maxduration = ref(60 * 1000);
	const iflytekAppId = "495edb9d";
	const iflytekApiKey = "673dc0f903ecea8bec3d3510651fd9ac";
	const iflytekApiSecret = "NjRiZDkxN2RmODM0YWUzZDZhN2YzYzhi";
	const typeAsr = ref("1");
	const btnStatus = ref("UNDEFINED");
	const btnStatusShow = ref("开始录音");
	const changeBtnStatus = (status) => {
		btnStatus.value = status;
		if (status === "CONNECTING") {
			btnStatusShow.value = "建立连接中";

			resultText.value = "";
			resultTextTemp.value = "";
		} else if (status === "OPEN") {
			btnStatusShow.value = "录音中";
		} else if (status === "CLOSING") {
			btnStatusShow.value = "关闭连接中";
		} else if (status === "CLOSED") {
			btnStatusShow.value = "开始录音";
		}
	};
	let iatWS;
	let resultText = ref("");
	let resultTextTemp = ref("");
	let resultTextOffline = ref("");
	const getWebSocketUrl = () => {
		// 请求地址根据语种不同变化
		var url = "wss://rtasr.xfyun.cn/v1/ws";
		var appId = iflytekAppId;
		var secretKey = iflytekApiKey;
		var ts = Math.floor(new Date().getTime() / 1000);
		var signa = hex_md5(appId + ts);
		var signatureSha = CryptoJSNew.HmacSHA1(signa, secretKey);
		var signature = CryptoJS.enc.Base64.stringify(signatureSha);
		signature = encodeURIComponent(signature);
		return `${url}?appid=${appId}&ts=${ts}&signa=${signature}&vadMdn=2`;
	};
	const renderResult = (resultData) => {
		let jsonData = JSON.parse(resultData);
		if (jsonData.action == "started") {
			// 握手成功
			console.log("握手成功");
		} else if (jsonData.action == "result") {
			const data = JSON.parse(jsonData.data);
			console.log(data);
			// 转写结果
			resultTextTemp.value = "";
			data.cn.st.rt.forEach((j) => {
				j.ws.forEach((k) => {
					k.cw.forEach((l) => {
						resultTextTemp.value += l.w;
					});
				});
			});
			if (data.cn.st.type == 0) {
				// 【最终】识别结果：
				resultText.value += resultTextTemp.value;
				resultTextTemp.value = "";
			}
		} else if (jsonData.action == "error") {
			// 连接发生错误
			console.log("出错了:", jsonData);
		}
	};
	const connectWebSocket = () => {
		const websocketUrl = getWebSocketUrl();
		if ("WebSocket" in window) {
			iatWS = new WebSocket(websocketUrl);
		} else if ("MozWebSocket" in window) {
			iatWS = new MozWebSocket(websocketUrl);
		} else {
			ElMessage.error("浏览器不支持WebSocket");
			return;
		}
		changeBtnStatus("CONNECTING");
		iatWS.onopen = (e) => {
			// 开始录音

			recStart();
		};
		iatWS.onmessage = (e) => {
			renderResult(e.data);
		};
		iatWS.onerror = (e) => {
			console.error(e);
			recStop();
			changeBtnStatus("CLOSED");
		};
		iatWS.onclose = (e) => {
			//recStop();
			changeBtnStatus("CLOSED");
		};
	};
	const recOpen = () => {
		rec.value = Recorder({
			type: "pcm", //录音格式，可以换成wav等其他格式
			sampleRate: 16000, //录音的采样率，越大细节越丰富越细腻
			bitRate: 16, //录音的比特率，越大音质越好
			onProcess: (
				buffers,
				powerLevel,
				bufferDuration,
				bufferSampleRate,
				newBufferIdx,
				asyncEnd
			) => {
				//录音实时回调，大约1秒调用12次本回调
				//可实时绘制波形，实时上传（发送）数据
				if (wave.value)
					wave.value.input(
						buffers[buffers.length - 1],
						powerLevel,
						bufferSampleRate
					);

				if (iatWS.readyState === iatWS.OPEN) {
					iatWS.send(buffers[buffers.length - 1]);
				}
				if (bufferDuration > maxduration.value) {
					recStop();
				}
			},
		});
		//打开录音，获得权限
		rec.value.open(
			() => {
				console.log("录音已打开");
				if (recwave.value) {
					//创建音频可视化图形绘制对象
					wave.value = Recorder.WaveView({ elem: recwave.value });
				}
			},
			(msg, isUserNotAllow) => {
				//用户拒绝了录音权限，或者浏览器不支持录音
				console.log(
					(isUserNotAllow ? "UserNotAllow，" : "") + "无法录音:" + msg
				);
			}
		);
	};

	const recStart = () => {
		if (!rec.value) {
			console.error("未打开录音");
			return;
		}
		rec.value.start();
		changeBtnStatus("OPEN");

		console.log("已开始录音");
	};
	const recStop = () => {
		if (!rec.value) {
			console.error("未打开录音");
			return;
		}
		rec.value.stop(
			(blob, duration) => {
				//blob就是我们要的录音文件对象，可以上传，或者本地播放
				recBlob.value = blob;
				//简单利用URL生成本地文件地址，此地址只能本地使用，比如赋值给audio.src进行播放，赋值给a.href然后a.click()进行下载（a需提供download="xxx.mp3"属性）
				var localUrl = (window.URL || webkitURL).createObjectURL(blob);
				console.log("录音成功", blob, localUrl, "时长:" + duration + "ms");

				//this.upload(blob); //把blob文件上传到服务器

				iatWS.send('{"end": true}');
				changeBtnStatus("CLOSING");
				//recBlob  pcm格式转为MP3
			},
			(err) => {
				console.error("结束录音出错：" + err);
			}
		);
	};
	const recPlay = () => {
		//本地播放录音试听，可以直接用URL把blob转换成本地播放地址，用audio进行播放
		var localUrl = URL.createObjectURL(recBlob.value);
		var audio = document.createElement("audio");
		audio.controls = true;
		document.body.appendChild(audio);
		audio.src = localUrl;
		audio.play(); //这样就能播放了
		//注意不用了时需要revokeObjectURL，否则霸占内存
		setTimeout(function () {
			URL.revokeObjectURL(audio.src);
		}, 5000);
	};
	const recdown = () => {
		if (!recBlob.value) {
			ElMessage.error("没有录音文件");
			return;
		}
		var downA = document.createElement("A");
		downA.href = (window.URL || webkitURL).createObjectURL(recBlob.value);
		downA.download = "录音文件.pcm";
		downA.click();
	};
	const recognition = () => {
		if (!recBlob.value) {
			ElMessage.error("找不到录音文件");
			return;
		}
		var api = "/api/v1/sound";
		var onreadystatechange = function (xhr) {
			return function () {
				if (xhr.readyState == 4) {
					if (xhr.status == 200 && JSON.parse(xhr.response).code == 200) {
						resultTextOffline.value = JSON.parse(xhr.response).data;
					} else {
						console.error("上传失败", xhr.status, xhr.responseText);
					}
				}
			};
		};

		/***方式二：使用FormData用multipart/form-data表单上传文件***/
		var form = new FormData();
		let recBlobWav = addWavHeader(recBlob.value, 16000, 16, 1);
		form.append("file", recBlobWav, "recorder.wav");
		//...其他表单参数
		form.append("way", typeAsr.value * 1);
		var xhr = new XMLHttpRequest();
		xhr.open("POST", api + "/recognition");
		xhr.onreadystatechange = onreadystatechange(xhr);
		xhr.send(form);
	};
	const btnclick = () => {
		if (btnStatus.value === "UNDEFINED" || btnStatus.value === "CLOSED") {
			connectWebSocket();
		} else if (btnStatus.value === "CONNECTING" || btnStatus.value === "OPEN") {
			// 结束录音
			recStop();
		}
	};
	const addWavHeader = (pcmData, sampleRate, bitsPerSample, numChannels) => {
		const dataLength = pcmData.byteLength;
		const header = new ArrayBuffer(44);
		const view = new DataView(header);

		// RIFF chunk
		writeString(view, 0, "RIFF");
		view.setUint32(4, 32 + dataLength, true);
		writeString(view, 8, "WAVE");

		// fmt chunk
		writeString(view, 12, "fmt ");
		view.setUint32(16, 16, true);
		view.setUint16(20, 1, true); // PCM
		view.setUint16(22, numChannels, true);
		view.setUint32(24, sampleRate, true);
		view.setUint32(28, sampleRate * (bitsPerSample / 8) * numChannels, true);
		view.setUint16(32, (bitsPerSample / 8) * numChannels, true);
		view.setUint16(34, bitsPerSample, true);

		// data chunk
		writeString(view, 36, "data");
		view.setUint32(40, dataLength, true);

		// Merge header and PCM data
		const wav = new Blob([header, pcmData], { type: "audio/wav" });

		// Helper function to write a string to an ArrayBuffer
		function writeString(view, offset, string) {
			for (let i = 0; i < string.length; i++) {
				view.setUint8(offset + i, string.charCodeAt(i));
			}
		}

		return wav;
	};
	onMounted(() => {
		recOpen();
	});
	onBeforeUnmount(() => {
		rec.value = null;
		rec.value && rec.value.close();
	});
</script>
<style scoped>
	.read-the-docs {
		color: #888;
	}
	.result-area {
		border: 1px solid #ccc;
		width: 400px;
		height: 150px;
		margin: 10px 0;
		overflow-y: scroll;
	}
</style>
